// Created by Windward Studios - no copyright is claimed. This code can be used in
// any manner by anyone for any reason. There is no copyright of any kind on it. You may
// use it in commercial products. You may change it without sharing those changes.
// We ask that you keep the "created by Windward Studios" in a comment at the top.

using System;
using System.Data.Common;
using System.IO;

namespace net.windward.utils.ado
{
	/// <summary>
	/// This is similiar to an ADO.NET DbProviderFactory and provides additional functionality for each provider. 
    /// 
    /// This is called WrVendor instead of WrProvider because it is different from DbProviderFactory and because
    /// we use the term Provider for the ODBC/OleDb providers. The ODBC/OleDb concept of a Provider (which is different from an ADO.NET DbProviderFactory) is handled as a property set in the
    /// WrProviderFactory class so all other cases do not need to implement a class for that layer.
    /// 
    /// All properties that indicate if a provider can perform a given task are provided here (as virtual properties), not in the child classes. 
    /// 
    /// This class can return IWrServer objects for a given server/instance that exists on a computer. 
    /// 
	/// </summary>
	public abstract class WrVendor : IComparable
	{
		///<summary>Determines which objects to return of the database objects.</summary>
		[Flags]
		public enum ELEM_OWNER
		{
			/// <summary>Return all user level tables, views, and stored procedures, including those not owned by the calling user.</summary>
			USER=0x03, // this is USER 0x01 | USER_OWNED 0x02
			/// <summary>Return all tables, views, and stored procedures owned by the calling user.</summary>
			USER_OWNED=0x02,
			/// <summary>Return all system level tables, views, and stored procedures, including those not owned by the calling user.</summary>
			SYSTEM=0x04,
			/// <summary>Return all tables, views, and stored procedures, both user and system, including those not owned by the calling user.</summary>
			ALL=0x07,
			///<summary>Returns the descriptions of each object in the metadata.</summary>
			DESCRIPTIONS = 0x10,
			///<summary>Returns the data type of each column in the metadata.</summary>
			COLUMN_TYPES = 0x20,
			///<summary>Returns the parameters, including type, for each stored procedure in the metadata.</summary>
			PROCEDURE_PARAMS = 0x40,
			///<summary>Returns the primary key:foreign key mapping for each table in the metadata.</summary>
			KEY_JOINS = 0x80,
			///<summary>Returns all meta data.</summary>
			META_DATA = 0xF0
		};

		/// <summary>
		/// If this vendor uses databases in the connection string.
		/// </summary>
		public enum DATABASE_MODE
		{
			/// <summary>
			/// A database is required.
			/// </summary>
			REQUIRED,
			/// <summary>
			/// A database is optional. This is used by the ODBC and OleDb vendors.
			/// </summary>
			OPTIONAL,
			/// <summary>
			/// Does not use a database. This is Oracle, Access, and Excel.
			/// </summary>
			NEVER
		}

		/// <summary>
		/// What must be included in the call to execute a stored procedure.
		/// </summary>
		[Flags]
		public enum SELECT_PARAMS
		{
			/// <summary>
			/// Just the IN parameters.
			/// </summary>
			NONE = 0,
			/// <summary>
			/// Declare the OUT parameters.
			/// </summary>
			OUT_PARAMS = 0x01,
			/// <summary>
			/// Declare the return value parameter.
			/// </summary>
			RETURN_PARAMS = 0x02,
		}

		/// <summary>The name of this vendor. Semicolons not allowed.</summary>
		protected string name;
		/// <summary>The description of this vendor</summary>
		protected string description;
		/// <summary>The provider class of this vendor. Semicolons not allowed.</summary>
		protected string providerClass;
		/// <summary>The .NET factory for this vendor</summary>
		protected DbProviderFactory providerFactory;
		/// <summary>The syntax for this vendor</summary>
		protected IWrSyntax syntax;

		/// <summary>
		/// Creates a vendor object.
		/// </summary>
		/// <param name="name">The friendly name of the provider. Semicolons not allowed.</param>
		/// <param name="description">The description of this provider (optional).</param>
		/// <param name="providerClass">The class of the provider. Semicolons not allowed.</param>
		/// <returns></returns>
		protected WrVendor(string name, string description, string providerClass)
		{
			this.name = name;
			this.description = description;
			this.providerClass = providerClass;
			syntax = null;
			try
			{
				if (! string.IsNullOrEmpty(providerClass))
					providerFactory = DbProviderFactories.GetFactory(providerClass);
			}
			catch (Exception)
			{
				providerFactory = null;
			}
		}
        /// <summary>
        /// Creates a vendor object.
        /// </summary>
        /// <param name="name">The friendly name of the provider. Semicolons not allowed.</param>
        /// <param name="description">The description of this provider (optional).</param>
		protected WrVendor(string name, string description)
		{
			this.name = name;
			this.description = description;
			providerClass = null;
			syntax = null;
			providerFactory = null;
		}

		#region Properties

		/// <summary>
		/// True if can enumerate running servers from this vendor. This is implemented seperately from
		/// CanCreateDataSourceEnumerator because DB2 and SqlServer implement CreateDataSourceEnumerator differently.
		/// </summary>
		public virtual bool CanEnumerateServers
		{
			get { return false; }
		}

		/// <summary>
		/// True if can enumerate databases on a server. This is implemented seperately from
		/// CanCreateDataSourceEnumerator because DB2 and SqlServer implement CreateDataSourceEnumerator differently.
		/// </summary>
		public virtual bool CanEnumerateDatabases
		{
			get { return false; }
		}

		/// <summary>
		/// True if can query metadata from the database.
		/// </summary>
		public virtual bool CanQueryMetadata
		{
			get { return false; }
		}

		/// <summary>
		/// True if can launch the administrator for this vendor. This will return true if it can find the admin
		/// program file. In that case the launch could still fail.
		/// </summary>
		public abstract bool CanLaunchAdministrator
		{
			get;
		}

		/// <summary>
		/// True if can run DDL scripts.
		/// </summary>
		public abstract bool CanRunScripts
		{
			get;
		}

		/// <summary>
		/// True if can use the WindowsIdentity of the caller eliminating the need to pass a username and password.
		/// </summary>
		public abstract bool CanUseTrustedConnection
		{
			get;
		}

		/// <summary>
		/// True if you need to select a provider for this vendor. The only known cases of this are ODBC and OldDb.
		/// </summary>
		public virtual bool HasProviders
		{
			get { return false; }
		}

		/// <summary>
		/// What must be included in the call to execute a stored procedure.
		/// </summary>
		public virtual SELECT_PARAMS SelectParams
		{
			get { return SELECT_PARAMS.NONE; }
		}

		/// <summary>
		/// True if this vendor has the concept of databases in an installed copy of their product. This is false for Oracle (which
		/// does not have the concept of a database) and for databases like Access that are just a file - and the file is the "server."
		/// </summary>
		public virtual DATABASE_MODE DatabaseMode
		{
			get { return DATABASE_MODE.REQUIRED; }
		}

		/// <summary>
		/// True if a database supports stored procedures.
		/// </summary>
		public virtual bool HasStoredProcedures
		{
			get { return true; }
		}

		/// <summary>
		/// Returns true if this connector is installed on the system. To be true the connector must be in the GAC and must
		/// be listed in machine.config.
		/// </summary>
		public virtual bool IsInstalled
		{
			get { return true; }
		}

		/// <summary>
		/// The name of this provider.
		/// </summary>
		public virtual string Name
		{
			get { return name; }
		}

		/// <summary>
		/// The description of this provider. This can be null.
		/// </summary>
		public string Description
		{
			get { return description; }
		}

		/// <summary>
        /// <para>The provider class for this provider.</para>
        /// <para>See the <see cref="DbProviderFactories.GetFactoryClasses"/> method. ProviderClass is the
        /// InvariantName of the provider.</para>
		/// </summary>
		public virtual string ProviderClass
		{
			get { return providerClass; }
		}

		/// <summary>
        /// <para>The provider factory subtype.  Semicolons not allowed.</para>
        /// <para>This is used in special WrVendors to help identify them. Specifically, WrProviderFactories.CreateProvider
        /// uses this property to determine which WrVendor to construct for such special WrVendors.  Currently (7/16/10), WrAccessVendor,
        /// WrExcelVendor, WrSPListVendor2, and WrSPListVendor are identified in this way by CreateProvider.</para>
		/// </summary>
		public virtual string ProviderSubtype
		{
			get { return ""; }
		}

		/// <summary>
		/// The name of the (ODBC/OleDb) providers for this (vendor) provider.
		/// </summary>
		/// <exception cref="WrDbException">Thrown if this (vendor) providers does not support (ODBC/OleDb) providers.</exception>
		public virtual string OdbcProvider
		{
			get { throw new WrDbException("Providers not supported"); }
			set { throw new WrDbException("Providers not supported"); }
		}

		#endregion

		#region main methods

		/// <summary>
		/// A connection string for a database on a server.
		/// </summary>
		/// <param name="server">The server to access (ex: localhost).</param>
		/// <param name="database">The database to connect to.</param>
		/// <param name="credentials">The user credentials to access the database.</param>
		/// <param name="showPassword">True to include the password, false to add the password as *****.</param>
		/// <returns>The connection string for this database on this server.</returns>
		public abstract string ConnectionString(string server, string database, WrCredentials credentials, bool showPassword);

		/// <summary>
		/// Returns the parameters from a connection string making the best guess. Will return null for items it could not determine.
		/// </summary>
		/// <param name="connectionString">The connection string to parse.</param>
		/// <returns>The parameters.</returns>
		public virtual WrConnectionParams ConnectionParams(string connectionString)
		{
			if (string.IsNullOrEmpty(connectionString))
				return new WrConnectionParams();

			DbConnectionStringBuilder builder = new DbConnectionStringBuilder();
			builder.ConnectionString = connectionString;
			string server = builder.ContainsKey("server") ? builder["server"] as string : null;
			string database = builder.ContainsKey("database") ? builder["database"] as string : null;
			string username = builder.ContainsKey("username") ? builder["username"] as string : null;
			string password = builder.ContainsKey("password") ? builder["password"] as string : null;
			return new WrConnectionParams(server, database, username, password);
		}

		/// <summary>
		/// Return all (ODBC/OleDb) providers for this (vendor) provider.
		/// </summary>
		/// <exception cref="WrDbException">Thrown if this (vendor) providers does not support (ODBC/OleDb) providers.</exception>
		/// <returns>The name of each provider.</returns>
		public virtual OdbcDriverInfo[] GetOdbcProviders()
		{
			throw new WrDbException("ODBC Providers not supported");
		}

		/// <summary>
		/// All servers from this vendor on the network. This may not get all due to routers not passing
		/// on a broadcast or delays in some servers responding.
		/// </summary>
		/// <exception cref="WrDbException">Thrown if this provider does not support enumerate servers.</exception>
		/// <returns>All servers found on the sub-net.</returns>
		public virtual IWrServer[] GetServers()
		{
			throw new WrDbException("enumerate servers not supported");
		}

		/// <summary>
		/// Run a DDL script against a server (specific install on that box).
		/// </summary>
		/// <param name="connStr">The connection string to connect with.</param>
		/// <param name="script">The DDL script.</param>
		/// <exception cref="WrDbException">Thrown if this provider does not support running a script.</exception>
		/// <returns>True if successful.</returns>
		public virtual bool RunScript(string connStr, StreamReader script)
		{
			throw new WrDbException("run script not supported");
		}

		/// <summary>
		/// Run a DDL script against a server (specific install on that box).
		/// </summary>
		/// <param name="server">The server to access (ex: localhost).</param>
		/// <param name="database">The database to run the script against (ex: Northwind).</param>
		/// <param name="script">The DDL script.</param>
		/// <param name="credentials">The user credentials to access the database.</param>
		/// <exception cref="WrDbException">Thrown if this provider does not support running a script.</exception>
		/// <returns>True if successful.</returns>
		public virtual bool RunScript(string server, string database, StreamReader script, WrCredentials credentials)
		{
			throw new WrDbException("run script not supported");
		}

		/// <summary>
		/// Launch the database admin tool.
		/// </summary>
		/// <returns>True if launched successfully.</returns>
		public virtual bool RunAdministrator()
		{
			throw new WrDbException("run administrator not supported");
		}

		/// <summary>
		/// Returns a new instance of the provider's class that implements the WrCommand class.
		/// </summary>
		/// <returns>A new instance of WrCommand.</returns>
		public abstract WrCommand CreateCommand();

		/// <summary>
		/// Returns a new instance of the provider's class that implements the WrConnection class.
		/// </summary>
		/// <returns>A new instance of WrCommand.</returns>
		public virtual WrConnection CreateConnection()
		{
			return new WrConnection(providerFactory);
		}

		/// <summary>
		/// Create a server object.
		/// </summary>
		/// <param name="server">The name of the server the database is on.</param>
		public abstract IWrServer CreateServer(string server);

		/// <summary>
		/// Returns a new instance of the provider's class that implements the WrParameter class.
		/// </summary>
		/// <returns>A new instance of WrCommand.</returns>
		public virtual DbParameter CreateParameter()
		{
			return providerFactory.CreateParameter();
		}

		#endregion

		#region helper methods

		/// <summary>
		/// Converts a select from the Kailua standard to the one for this database. Presently handles "top N"
		/// </summary>
		/// <param name="select">The normalized select.</param>
		/// <returns>The database specific select.</returns>
		public virtual string ConvertSelect(string select)
		{
			if (syntax != null)
				return syntax.ConvertSelect(select);
			return select;
		}

		/// <summary>
		/// Create a safe table name. Generally needed only if the name has a space in it or is a reserved name.
		/// </summary>
		/// <param name="tableName">The true table name.</param>
		/// <returns>The table name as it should be used in a select.</returns>
		public virtual string SafeTableName (string tableName)
		{
			if (syntax != null)
				return syntax.SafeTableName(tableName);
			return tableName;
		}

		/// <summary>
		/// Create a safe column name. Generally needed only if the name has a space in it or is a reserved name.
		/// </summary>
		/// <param name="columnName">The true column name.</param>
		/// <returns>The table name as it should be used in a select.</returns>
		public virtual string SafeColumnName(string columnName)
		{
			if (syntax != null)
				return syntax.SafeColumnName(columnName);
			return columnName;
		}

		/// <summary>
		/// Create a safe table.column name. Generally returns "tableName.columnName".
		/// </summary>
		/// <param name="tableName">The true table name.</param>
		/// <param name="columnName">The true column name.</param>
		/// <returns>The table.column name as it should be used in a select.</returns>
		public virtual string SafeTableColumnName(string tableName, string columnName)
		{
			if (syntax != null)
				return syntax.SafeTableColumnName(tableName, columnName);
			return SafeTableName(tableName) + "." + SafeColumnName(columnName);
		}

		#endregion

		#region IComparable Members

		///<summary>
		///Compares the current instance with another object of the same type.
		///</summary>
		///
		///<returns>
		///A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance is less than obj. Zero This instance is equal to obj. Greater than zero This instance is greater than obj. 
		///</returns>
		///
		///<param name="obj">An object to compare with this instance. </param>
		///<exception cref="T:System.ArgumentException">obj is not the same type as this instance. </exception><filterpriority>2</filterpriority>
		public int CompareTo(object obj)
		{
			WrVendor vnd = obj as WrVendor;
			if (vnd == null)
				return 0;
			return Name.CompareTo(vnd.Name);
		}

		#endregion

		/// <summary>
		/// An OleDb or Odbc driver loaded on the system.
		/// </summary>
		public class OdbcDriverInfo : IComparable
		{
			private readonly string name;
			private readonly string driver;

			/// <summary>
			/// Create the object.
			/// </summary>
			/// <param name="name">The display name of the driver.</param>
			/// <param name="driver">The actual name of the provider.</param>
			public OdbcDriverInfo(string name, string driver)
			{
				this.name = name;
				this.driver = driver;
			}

			/// <summary>
			/// The display name of the driver.
			/// </summary>
			public string Name
			{
				get { return name; }
			}

			/// <summary>
			/// The actual name of the provider.
			/// </summary>
			public string Driver
			{
				get { return driver; }
			}

			///<summary>
			///Returns a <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
			///</summary>
			///<returns>
			///A <see cref="T:System.String"></see> that represents the current <see cref="T:System.Object"></see>.
			///</returns>
			///<filterpriority>2</filterpriority>
			public override string ToString()
			{
				return name;
			}

			///<summary>
			///Compares the current instance with another object of the same type.
			///</summary>
			///
			///<returns>
			///A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: Value Meaning Less than zero This instance is less than obj. Zero This instance is equal to obj. Greater than zero This instance is greater than obj. 
			///</returns>
			///
			///<param name="obj">An object to compare with this instance. </param>
			///<exception cref="T:System.ArgumentException">obj is not the same type as this instance. </exception><filterpriority>2</filterpriority>
			public int CompareTo(object obj)
			{
				OdbcDriverInfo odi = obj as OdbcDriverInfo;
				if (odi == null)
					return 0;
				return name.CompareTo(odi.name);
			}
		}
	}
}
